"""
聊天室思路分析
    1. 需求分析: 干什么达到什么效果?
    2. 技术点的确认:
        数据如何流动?
            通过转发, 客户端 发送给 服务器 --> 转发到其他客户端
        网络模型如何构建?
            使用 udp数据报套接字 方案构建
        用户信息在哪儿维护,怎么维护?
            所有的数据都在服务器处理, 方便管理维护. 用户数据使用 列表套元组形式[(name, address), (name1, address)] 存储
        随意收发消息如何避免阻塞?
            收发消息分别使用不同的进程执行.
    3. 分析功能模块
        网络搭建
        进入聊天室
        聊天
        退出聊天室
        管理员发送消息

    基于socket udp协议 fork多线程
        接收分类:
            进入聊天群:
                客户端: --> 发送请求--> 输入姓名, 让进入群聊, 可以聊天, 接收结果, 否则重新输入姓名
                服务器: --> 接收请求--> 判断姓名是否存在, 不存在则保存姓名,再邀请客户端, 通知其他客户端, 否则拒绝
            聊天:
                客户端: 创建新的进程, 一个进程循环发送消息, 一个进程循环接收消息
                服务器: 接收请求, 判断请求类型, 将消息转发给其他人
            离开:

    通讯协议设置(服务器):
    进入聊天群: L代号
    聊天: C代号
    退出: Q代号
    服务器反馈: ok 表示成功, 其他表示失败.
"""
from socket import *
import os


# class ChatServerss:  # 废弃
#     def __init__(self):
#         self.__person = {}  # 存储群聊里的人

#         # signal(SIGCHLD, SIG_IGN)  # 系统处理僵尸进程
#         self.__sockfd = socket(AF_INET, SOCK_DGRAM)  # 实例化套接字
#         self.__server_ip = ("127.0.0.1", 18693)
#         self.__sockfd.bind(self.__server_ip)  # 绑定服务器地址

#         # 定义通讯协议
#         self.__msg = 'L'  # 进入群聊
#         self.__group_chat = 'C'  # 聊天
#         self.__quit = 'Q'  # 退出

#     # 消息收发
#     def server_start(self):
#         # 开启多进程处理
#         pid = os.fork()
#         if pid < 0:
#             print("开启进程失败!")
#         elif pid == 0: # 子进程处理管理员消息
#             while True:
#                 info = input("管理员消息: ")
#                 info = f"C 管理员 {info}"
#                 print(info)
#                 self.__sockfd.sendto(info.encode(), self.__server_ip)
#         else:  # 处理客户端请求
#             self.do_request()  # 接收请求

#     # 请求处理函数
#     def do_request(self):
#         """
#         处理客户端请求,并且分类
#         """
#         # 循环处理请求
#         while True:
#             data, addr = self.__sockfd.recvfrom(1024)  # 接收
#             print(f"有客户端连接: {addr}, 客户端发送的消息: {data.decode()}")

#             # 按照协议进行处理
#             self.agr_dispose(addr, data)

#     def agr_dispose(self, addr, data):
#         """按照协议进行处理"""
#         date, sl, info = data.decode().split(' ')[0], data.decode().split(' ')[1], data.decode().split(' ')[2]
#         print(date,sl,info)
        
#         if date == self.__msg:  # 申请进入聊天群
#             self.do_login(addr, data)
#         elif date == self.__group_chat:  # 群聊聊天
#             self.group(addr, data, info)

#         elif date == self.__quit:  # 退出
#             self.chilent_exit(sl, addr)
#             # 群发
#             for i in self.__person:
#                 self.__sockfd.sendto(f"Q {sl} {sl}退出群聊.".encode(), self.__person[i])
    
#     # 退出函数
#     def chilent_exit(self, name, addr):
#         # 回复客户端,确认退出
#         self.__sockfd.sendto(f"Q {name}".encode(), addr)
        
#         # 从群里删除
#         del self.__sockfd[name]

#     # 群发
#     def group(self, addr, data, info):
#         """群发"""

#         # 发送的人
#         jb = data.decode().split(' ')
#         data_, t, name = jb[0], jb[1], jb[2]  # 解包

#     def do_login(self, addr, data):
#         """登录处理"""
#         if data.decode().split(' ')[1] in self.__person:  # 如果有相同的名字则返回 有相同名字
#             self.__sockfd.sendto("有相同名字了, 请重试!".encode(), addr)  # 回复客户端
#             return
#         self.__sockfd.sendto("ok 昵称创建成功,已加入群聊!".encode(), addr)  # 没有相同的名字,回复客户端ok

#         # 群聊转发, 欢迎谁谁入群.
#         for i in self.__person:
#             self.__sockfd.sendto(f"欢迎'{data.decode().split(' ')[1]}'进入群聊.".encode(),
#                                  self.__person[i])  # 提取名字 + 转发群里所有人

#         self.__person[data.decode().split(' ')[1]] = addr  # 追加字典保存信息

#     # 实例化


class CharServer:
    def __init__(self):
        self.__sockfd = socket(AF_INET, SOCK_DGRAM)
        self.__server_ip = ("0.0.0.0", 18693)
        self.__sockfd.bind(self.__server_ip)

        self.__person = {}
    
    # 处理登录
    def __do_login(self, name, addr):
        if name in self.__person or "管理员" in name or "超级管理员" in name:  # 名字不能重名和几个系统用的关键字
            self.__sockfd.sendto(f"{name}该用户存在.".encode(), addr)
            return

        # 不存在则加入字典
        msg = f"\n欢迎'{name}'进入聊天群."
        for i in self.__person: # 群发
            self.__sockfd.sendto(msg.encode(), self.__person[i])
        self.__person[name] = addr # 添加
        self.__sockfd.sendto(b'ok', addr) # 回复客户端
    
    # 处理聊天
    def __do_chat(self, name, info):
        # 群发
        msg = f"{name}: {info}"
        for i in self.__person:
            if i != name: # 除开本人
                self.__sockfd.sendto(msg.encode(), self.__person[i])
    
    # 处理退出
    def __do_quit(self, name):
        msg = f"\n'{name}'退出了群聊."
        
        # 群发
        for i in self.__person:
            if i == name: # 本人发
                self.__sockfd.sendto(b'exit', self.__person[i])
            else: # 其他人发
                self.__sockfd.sendto(msg.encode(), self.__person[i])
        
        # 删除退出的用户
        del self.__person[name]
    
    # 接收请求处理,分发不同处理函数
    def __do_requsest(self):
        """接收请求处理"""
        while True:
            data, addr = self.__sockfd.recvfrom(9216)
            print(data.decode())
            info = data.decode().split(' ')
            if info[0] == 'L':
                self.__do_login(info[1], addr) # 参数, 名字和地址
            elif info[0] == 'C':
                self.__do_chat(info[1], info[2]) # 参数, 名字和群员发送的内容
            elif info[0] == 'Q':
                if info[1] in self.__person:
                    self.__do_quit(info[1]) # 参数: info[1] 是名字
      
    # main
    def start(self):
        """开始"""
        pid = os.fork()
        if pid < 0:
            print("创建进程失败...")
        elif pid == 0: # 子进程不能去变量用户字典, 两个虽然有一样的字典,但是数据不互通
            while True:
                info = input("管理员消息:")
                info = "C 管理员 " + info  # 给自己发送, 解析C 达到转发所有人目的
                self.__sockfd.sendto(info.encode(), self.__server_ip)
        else:
            self.__do_requsest()
              

so = CharServer()
so.start()
